package com.zed.infrastructure.persistence.mybatis.plus.core.handler;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.handler.BaseJsonTypeHandler;
import com.mybatisflex.core.handler.JacksonTypeHandler;

import java.io.IOException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;

/**
 * 自定义字段处理器,复制于{@link JacksonTypeHandler}
 *
 * @author Zed
 */
public class ZedJacksonTypeHandler extends BaseJsonTypeHandler<Object> {

    private static DateTimeFormatter LOCAL_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");

    private static DateTimeFormatter LOCAL_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static ObjectMapper objectMapper;
    private final Class<?> propertyType;
    private Class<?> genericType;
    private JavaType javaType;

    public ZedJacksonTypeHandler(Class<?> propertyType) {
        this.propertyType = propertyType;
    }

    public ZedJacksonTypeHandler(Class<?> propertyType, Class<?> genericType) {
        this.propertyType = propertyType;
        this.genericType = genericType;
    }

    @Override
    protected Object parseJson(String json) {
        try {
            if (genericType != null && Collection.class.isAssignableFrom(propertyType)) {
                return getObjectMapper().readValue(json, getJavaType());
            } else {
                return getObjectMapper().readValue(json, propertyType);
            }
        } catch (IOException e) {
            throw FlexExceptions.wrap(e, "Can not parseJson by JacksonTypeHandler: " + json);
        }
    }

    @Override
    protected String toJson(Object object) {
        try {
            return getObjectMapper().writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw FlexExceptions.wrap(e, "Can not convert object to Json by JacksonTypeHandler: " + object);
        }
    }


    public JavaType getJavaType() {
        if (javaType == null) {
            javaType = getObjectMapper().getTypeFactory().constructCollectionType((Class<? extends Collection>) propertyType, genericType);
        }
        return javaType;
    }

    public static ObjectMapper getObjectMapper() {
        if (null == objectMapper) {
            objectMapper = new ObjectMapper();
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(Date.class, new DateSerializer());
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
            javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
            javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer());

            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
            javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer());
            javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
            objectMapper.registerModule(javaTimeModule);
        }
        return objectMapper;
    }

    /**
     * 序列化实现
     */
    private static class LocalTimeSerializer extends JsonSerializer<LocalTime> {
        @Override
        public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null) {
                gen.writeString(value.format(LOCAL_TIME_FORMAT));
            }
        }
    }

    /**
     * 序列化实现
     */
    private static class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null) {
                long timestamp = value.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
                gen.writeNumber(timestamp);
            }
        }
    }

    /**
     * 序列化实现
     */
    private static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null) {
                long timestamp = value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                gen.writeNumber(timestamp);
            }
        }
    }

    /**
     * 反序列化实现
     */
    private static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext)
                throws IOException {
            long timestamp = p.getValueAsLong();
            if (timestamp > 0) {
                return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
            } else {
                //兼容字符串格式时间传入
                String value = p.getValueAsString();
                return LocalDateTimeUtil.of(DateUtil.parse(value));
            }
        }
    }

    /**
     * 反序列化实现
     */
    private static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext deserializationContext)
                throws IOException {
            long timestamp = p.getValueAsLong();
            if (timestamp > 0) {
                return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()).toLocalDate();
            } else {
                //兼容字符串格式时间传入
                String value = p.getValueAsString();
                return LocalDate.parse(value, LOCAL_DATE_FORMAT);
            }
        }
    }


    /**
     * 反序列化实现
     */
    private static class LocalTimeDeserializer extends JsonDeserializer<LocalTime> {
        @Override
        public LocalTime deserialize(JsonParser p, DeserializationContext deserializationContext)
                throws IOException {
            return LocalTime.parse(p.getValueAsString(), LOCAL_TIME_FORMAT);
        }
    }

    public static void setObjectMapper(ObjectMapper objectMapper) {
        ZedJacksonTypeHandler.objectMapper = objectMapper;
    }
}
